package httpmvc

import (
	"bytes"
	"encoding/json"
	"fmt"
	"httpclient/dbutil"
	"io"
	"io/fs"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/lxn/walk"
	. "github.com/lxn/walk/declarative"
)

const (
	LF = "\r\n"
)

var (
	clickFlag = true
	headerMap = make(map[string]string, 0)
	cookieMap = make(map[string]string, 0)
)

type HttpMvcCli struct {
	data         *HttpMvcData
	form         *walk.MainWindow
	addr         *walk.LineEdit
	fileDir      *walk.ComboBox
	workDir      *walk.LineEdit
	selectFile   *walk.ComboBox
	selectMethod *walk.ComboBox
	selectOld    *walk.ComboBox
	dataPool     *walk.TextEdit
	input        *walk.TextEdit
	output       *walk.TextEdit
}

type ComboBoxModel struct {
	Params string
	Method string
	Title  string
	Key    string
}

type HttpMvcData struct {
	Name              string                 `json:"name"`
	Addr              string                 `json:"addr"`
	FileDir           []ComboBoxModel        `json:"fileDir"`
	SelectFile        string                 `json:"selectFile"`
	SelectFileIndex   int                    `json:"selectFileIndex"`
	SelectMethod      string                 `json:"selectMethod"`
	SelectMethodIndex int                    `json:"selectMethodIndex"`
	SelectFileModel   []ComboBoxModel        `json:"selectFileModel"`
	SelectMethodModel []ComboBoxModel        `json:"selectMethodModel"`
	DataPool          string                 `json:"dataPool"`
	Input             string                 `json:"input"`
	Method            string                 `json:"method"`
	WorkDir           string                 `json:"workDir"`
	Maps              map[string]interface{} `json:"maps"`
}

func (h *HttpMvcCli) Show() {
	h.data = new(HttpMvcData)

	h.data.SelectFileModel = make([]ComboBoxModel, 1)
	h.data.SelectMethodModel = make([]ComboBoxModel, 1)
	b := dbutil.Get("WorkSave")
	if len(b) > 0 {
		fmt.Println(b)
		fmt.Println(string(b))
		h.data.WorkDir = string(b)
	}

	_, err := MainWindow{
		AssignTo: &h.form,
		Title:    "HTTP MVC",
		Size:     Size{600, 800},
		Layout:   VBox{},
		Children: []Widget{
			Composite{
				Layout: HBox{MarginsZero: true},
				Children: []Widget{
					/* Label{
						Text: "历史记录",
					},
					ComboBox{
						AssignTo:      &h.selectOld,
						Editable:      true,
						MaxSize:       Size{300, 0},
						BindingMember: "Name",
						DisplayMember: "Name",
						Model:         dbutil.ReadAll[HttpMvcData](),
						OnCurrentIndexChanged: func() {
							if !clickFlag || h.selectOld.CurrentIndex() < 0 {
								return
							}
							go sleep()
							h.data = h.selectOld.Model().([]*HttpMvcData)[h.selectOld.CurrentIndex()]
							h.setData()
							h.readFileDirPath()
						},
					}, */

					Label{
						Text: "工作目录",
					},
					LineEdit{
						AssignTo: &h.workDir,
						MaxSize:  Size{200, 0},
						Text:     h.data.WorkDir,
						OnMouseDown: func(x, y int, button walk.MouseButton) {
							h.reWork()
						},
					},
					Label{
						Text: "应用目录",
					},
					ComboBox{
						AssignTo:      &h.fileDir,
						BindingMember: "Title",
						DisplayMember: "Title",
						Model:         h.data.FileDir,
						OnCurrentIndexChanged: func() {
							if !clickFlag || h.fileDir.CurrentIndex() < 0 {
								return
							}
							go sleep()
							h.readFileDirPath()

							/*  */
						},
					},
					Label{
						Text: "接口地址",
					},
					LineEdit{
						AssignTo: &h.addr,
						MaxSize:  Size{200, 0},
						Text:     h.data.Addr,
					},
					PushButton{
						Text: "保存",
						OnClicked: func() {
							h.bindSave()
							key := filepath.Join(h.workDir.Text(), h.fileDir.Text(), h.selectFile.Text(), h.selectMethod.Text())
							if key == "" {
								return
							}
							result := walk.MsgBox(h.form, "保存", "确认保存？", walk.MsgBoxYesNo)
							if result == 7 {
								return
							}
							dbutil.Write(key, h.data)
							h.data.SelectMethodIndex = 0
							h.data.SelectFileIndex = 0
						},
					},
					PushButton{
						Text: "删除",
						OnClicked: func() {
							result := walk.MsgBox(h.form, "删除", "确认删除？", walk.MsgBoxYesNo)
							if result == 7 {
								return
							}
							//todo
							dbutil.Delete(h.selectOld.Text())
							h.selectOld.SetModel(dbutil.ReadAll[HttpMvcData]())
						},
					},
				},
			},
			Composite{
				Layout: HBox{MarginsZero: true},
				Children: []Widget{

					Label{
						Text: "模块名称",
					},
					ComboBox{
						AssignTo:      &h.selectFile,
						Editable:      false,
						BindingMember: "Title",
						DisplayMember: "Title",
						CurrentIndex:  h.data.SelectFileIndex,
						MinSize:       Size{400, 0},
						Model:         h.data.SelectFileModel,
						OnCurrentIndexChanged: func() {
							if !clickFlag || h.selectFile.CurrentIndex() < 0 {
								return
							}
							go sleep()
							item := h.data.SelectFileModel[h.selectFile.CurrentIndex()]
							h.data.SelectFile = item.Key
							h.data.SelectFileIndex = h.selectFile.CurrentIndex()
							go h.readMethod()

						},
					},
					Label{
						Text: "请求路径",
					},
					ComboBox{
						AssignTo:      &h.selectMethod,
						Editable:      true,
						BindingMember: "Title",
						DisplayMember: "Title",
						MinSize:       Size{370, 0},
						CurrentIndex:  h.data.SelectMethodIndex,
						Model:         h.data.SelectMethodModel,
						OnCurrentIndexChanged: func() {
							if !clickFlag || h.selectFile.CurrentIndex() < 0 {
								return
							}
							go sleep()

							item := h.data.SelectMethodModel[h.selectMethod.CurrentIndex()]
							datas := dbutil.ReadAll[HttpMvcData]()
							for _, v := range datas {
								fmt.Println(v)
								if v.SelectMethod == item.Key && h.selectFile.Text() == v.SelectFile {
									h.data.DataPool = v.DataPool
									h.data.Addr = v.Addr
									break
								}
							}
							h.data.SelectMethod = item.Key
							h.data.Method = item.Method
							h.input.SetText(item.Params)
							h.data.SelectMethodIndex = h.selectMethod.CurrentIndex()
						},
					},
					PushButton{
						Text:    "发送",
						MinSize: Size{110, 0},
						OnClicked: func() {
							go h.sendHttp()
						},
					},
					PushButton{
						Text:    "切换",
						MinSize: Size{110, 0},
						OnClicked: func() {
							flag := h.output.Visible()
							h.input.SetVisible(flag)
							h.output.SetVisible(!flag)
						},
					},
				},
			},
			Composite{
				Layout: HBox{MarginsZero: true},
				Children: []Widget{
					TextEdit{
						VScroll:   true,
						AssignTo:  &h.input,
						TextColor: walk.RGB(0, 0, 225),
						Text:      h.data.Input,
					},
					TextEdit{
						VScroll:  true,
						Visible:  false,
						ReadOnly: true,
						AssignTo: &h.output,
					},
					TextEdit{
						VScroll:   true,
						MaxSize:   Size{400, 0},
						AssignTo:  &h.dataPool,
						TextColor: walk.RGB(0, 0, 225),
						Text:      h.data.DataPool,
					},
				},
			},
		},
	}.Run()
	if err != nil {
		panic(err)
	}
}

func sleep() {
	clickFlag = false
	time.Sleep(time.Second * 1)
	clickFlag = true
}

func (h *HttpMvcCli) reWork() {
	if !clickFlag || h.workDir.Text() == "" {
		return
	}
	go sleep()
	h.workDir.SetText(strings.ReplaceAll(h.workDir.Text(), "\\", "/"))
	dirs, err := os.ReadDir(h.workDir.Text())
	if err != nil {
		return
	}
	h.data.FileDir = make([]ComboBoxModel, 0)
	for _, d := range dirs {
		if strings.HasPrefix(d.Name(), ".") || strings.Contains(d.Name(), "logs") {
			continue
		}
		if d.IsDir() {
			h.data.FileDir = append(h.data.FileDir, ComboBoxModel{Title: d.Name(), Key: d.Name()})
		}
	}
	h.fileDir.SetModel(h.data.FileDir)
	dbutil.WriteString("WorkSave", h.workDir.Text())
}
func (h *HttpMvcCli) bindSave() {
	h.data.Addr = h.addr.Text()
	h.data.SelectFile = h.selectFile.Text()
	h.data.SelectMethod = h.selectMethod.Text()
	h.data.DataPool = h.dataPool.Text()
	h.data.Input = h.input.Text()
	h.data.WorkDir = h.workDir.Text()
	h.data.SelectFileModel = h.selectFile.Model().([]ComboBoxModel)
	h.data.SelectMethodModel = h.selectMethod.Model().([]ComboBoxModel)
	h.data.Maps = h.readToMapAll(h.dataPool.Text())
}

func (h *HttpMvcCli) setData() {
	h.addr.SetText(h.data.Addr)
	h.fileDir.SetModel(h.data.FileDir)
	h.selectFile.SetText(h.data.SelectFile)
	h.selectMethod.SetText(h.data.SelectMethod)
	h.dataPool.SetText(h.data.DataPool)
	h.input.SetText(h.data.Input)
	h.selectFile.SetModel(h.data.SelectFileModel)
	h.selectMethod.SetModel(h.data.SelectFileModel)
	h.workDir.SetText(h.data.WorkDir)
}

func (h *HttpMvcCli) readFileDirPath() {
	if h.fileDir.Text() == "" || h.workDir.Text() == "" {
		return
	}
	filePath := path.Join(h.workDir.Text(), h.fileDir.Text())
	_, err := os.Stat(filePath)
	if err != nil {
		walk.MsgBox(h.form, "警告", "无效目录", walk.MsgBoxOK)
		return
	}
	h.data.SelectFileModel = make([]ComboBoxModel, 0)
	filepath.Walk(filePath, func(path string, info fs.FileInfo, err error) error {
		if info.IsDir() || !strings.HasSuffix(info.Name(), "Controller.java") {
			return nil
		}
		h.data.SelectFileModel = append(h.data.SelectFileModel, ComboBoxModel{Title: info.Name(), Key: path})
		return nil
	})
	h.data.SelectMethodModel = make([]ComboBoxModel, 0)
	h.selectFile.SetModel(h.data.SelectFileModel)
}

func (h *HttpMvcCli) readMethod() {
	bs, err := os.ReadFile(h.data.SelectFile)
	if err != nil {
		walk.MsgBox(h.form, "警告", "无效文件！", walk.MsgBoxOK)
		return
	}
	buf := bytes.NewBuffer(bs)
	h.data.SelectMethodModel = make([]ComboBoxModel, 0)
	h.selectMethod.SetText("")
	var prefix string
	flag := true
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			break
		}
		if !strings.Contains(line, "@") || !strings.Contains(line, "Mapping") {
			continue
		}
		comp := regexp.MustCompile(`@[\w]+Mapping[{)"}(/-_\w+\d]+`)
		v := comp.FindString(line)
		if v == "" {
			continue
		}
		comp = regexp.MustCompile(`[{}/-_\w+\d]+`)
		vs := comp.FindAllString(v, -1)
		var method string
		var params string
		switch vs[0] {
		case "@RequestMapping":
			if flag {
				prefix = vs[1]
				flag = false
				continue
			}
			method = "POST"
			params = h.readParams(buf)
		case "@PostMapping":
			method = "POST"
			params = h.readParams(buf)
		case "@GetMapping":
			method = "GET"
			params = h.readParams(buf)
		case "@PutMapping":
			method = "PUT"
			params = h.readParams(buf)
		case "@DeleteMapping":
			method = "DELETE"
		}
		key := path.Join(prefix, vs[1])
		h.data.SelectMethodModel = append(h.data.SelectMethodModel, ComboBoxModel{Title: key, Key: key, Method: method, Params: params})
	}
	h.selectMethod.SetModel(h.data.SelectMethodModel)
}

func (h *HttpMvcCli) readParams(buf *bytes.Buffer) string {
	var name string
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			return ""
		}
		comp := regexp.MustCompile(`\(\)`)
		v := comp.FindString(line)
		if v != "" {
			fmt.Println(v)
			return ""
		}
		comp = regexp.MustCompile(` \w+\s\w+\)`)
		v = comp.FindString(line)
		if v == "" {
			continue
		}
		name = strings.Split(strings.TrimSpace(v), " ")[0] + ".java"
		break
	}
	params := bytes.Buffer{}
	filepath.Walk(h.workDir.Text(), func(path string, info fs.FileInfo, e error) error {
		if info.IsDir() {
			return nil
		}
		if info.Name() != name {
			return nil
		}
		bs, err := os.ReadFile(path)
		if err != nil {
			return err
		}
		buf := bytes.NewBuffer(bs)
		for {
			d, err := buf.ReadString('\n')
			if err != nil {
				break
			}
			comp := regexp.MustCompile(`private\s\w+\s\w+;`)
			v := comp.FindString(d)
			if v == "" {
				continue
			}
			s := strings.Split(v, " ")[2]
			key := strings.ReplaceAll(s, ";", "")
			params.WriteString(key)
			params.WriteString(" = ")
			params.WriteString(LF)
		}
		return nil
	})
	return params.String()
}

func (h *HttpMvcCli) readToMapAll(text string) map[string]interface{} {
	buf := bytes.NewBufferString(text)
	data := make(map[string]interface{}, 0)
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			break
		}
		if !strings.Contains(line, "=") {
			continue
		}
		key := strings.TrimSpace(line[0:strings.Index(line, "=")])
		value := strings.TrimSpace(line[strings.Index(line, "=")+1:])
		value = strings.ReplaceAll(value, LF, "")
		data[key] = value
	}
	return data
}

func (h *HttpMvcCli) readToMap(text string) (map[string]string, map[string]string) {
	buf := bytes.NewBufferString(text)
	data := make(map[string]string, 0)
	headers := make(map[string]string, 0)
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			break
		}
		if !strings.Contains(line, "=") {
			continue
		}
		key := strings.TrimSpace(line[0:strings.Index(line, "=")])
		value := strings.TrimSpace(line[strings.Index(line, "=")+1:])
		value = strings.ReplaceAll(value, LF, "")
		if strings.HasPrefix(line, "headers.") {
			headers[key] = value
			continue
		}
		data[key] = value
	}
	return headers, data
}

func (h *HttpMvcCli) sendHttp() {
	headers, data := h.readToMap(h.input.Text() + "\n")
	for k, v := range data {
		if v == "" {
			pv := h.readDataPool(k)
			if pv == "" {
				delete(data, k)
				continue
			}
			data[k] = pv
		}
	}
	for k, v := range headers {
		if v == "" {
			headers[k] = h.readDataPool(k)
		}
	}
	dataJson, _ := json.Marshal(data)

	url := h.addr.Text() + path.Join(h.selectMethod.Text())
	comp := regexp.MustCompile(`{\w+}`)
	vs := comp.FindAllString(url, -1)
	for _, v := range vs {
		comp := regexp.MustCompile(`\w+`)
		k := comp.FindString(v)
		for key, value := range data {
			if strings.TrimSpace(k) == strings.TrimSpace(key) {
				url = strings.ReplaceAll(url, strings.TrimSpace(v), strings.TrimSpace(value))
				break
			}
		}
	}
	client := &http.Client{}
	req, err := http.NewRequest(h.data.Method, url, bytes.NewReader(dataJson))
	h.outputPrintln("处理中...")
	req.Header.Add("content-type", "application/json")
	for k, v := range headerMap {
		req.Header.Add(k, v)
	}
	for k, v := range cookieMap {
		ck := &http.Cookie{Name: k, Value: v, HttpOnly: true}
		req.AddCookie(ck)
	}
	if err != nil {
		h.outputPrintln(h.data.Method+"："+url, "请求数据："+string(dataJson))
		return
	}
	for k, v := range headers {
		req.Header.Add(strings.ReplaceAll(k, "headers.", ""), v)
	}
	resp, err := client.Do(req)
	if err != nil {
		log.Println(err)
		h.outputPrintln(h.data.Method+"："+url, "请求数据："+string(dataJson))
		return
	}
	defer resp.Body.Close()
	bs, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Println(err)
		h.outputPrintln(h.data.Method+"："+url, "请求数据："+string(dataJson))
		return
	}
	var resultMap map[string]interface{}
	err = json.Unmarshal(bs, &resultMap)
	if err != nil {
		log.Println(err)
		h.outputPrintln(h.data.Method+"："+url, "请求数据："+string(dataJson))
		return
	}

	h.output.SetText(string(bs))
	h.mapFor(resultMap)
	printInfo := bytes.Buffer{}
	for k, v := range h.data.Maps {
		if k == "data" || k == "code" || k == "message" || k == "msg" || v == "" || v == "null" {
			continue
		}
		printInfo.WriteString(k + " = " + fmt.Sprint(v))
		printInfo.WriteString(LF)
	}
	h.outputPrintln(h.data.Method+"："+url, "请求数据："+string(dataJson), "响应信息："+string(bs))
	h.dataPool.SetText(printInfo.String())
	for k, v := range resp.Header {
		if k == "Date" || k == "Vary" || k == "Content-Type" {
			continue
		}
		if len(v) >= 1 {
			headerMap[k] = v[0]
		}
	}
	for _, v := range resp.Cookies() {
		cookieMap[v.Name] = v.Value
	}
}

func (h *HttpMvcCli) mapFor(vmap map[string]interface{}) {
	for k, v := range vmap {
		mp, ok := v.(map[string]interface{})
		if !ok {
			h.data.Maps[k] = v
		} else {
			h.mapFor(mp)
		}
	}
}

func (h *HttpMvcCli) readDataPool(key string) string {
	headers, data := h.readToMap(h.dataPool.Text() + "\n")
	v := data[key]
	if v == "" {
		v = headers[key]
	}
	return v
}

func (h *HttpMvcCli) outputPrintln(data ...string) {
	line := ""
	for _, v := range data {
		line += v + LF
	}
	h.output.SetText(line)
	h.output.SetVisible(true)
	h.input.SetVisible(false)
}
